package com.ikingtech.framework.sdk.web.support.handler;

import com.ikingtech.framework.sdk.context.exception.FrameworkException;
import com.ikingtech.framework.sdk.utils.Tools;
import com.ikingtech.framework.sdk.web.annotation.Encrypted;
import org.jsoup.Jsoup;
import org.jsoup.safety.Safelist;
import org.springframework.core.MethodParameter;
import org.springframework.http.HttpHeaders;
import org.springframework.http.HttpInputMessage;
import org.springframework.http.MediaType;
import org.springframework.http.converter.HttpMessageConverter;
import org.springframework.lang.NonNull;
import org.springframework.web.bind.annotation.RestControllerAdvice;
import org.springframework.web.servlet.mvc.method.annotation.RequestBodyAdviceAdapter;

import java.io.ByteArrayInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.lang.reflect.Type;
import java.nio.charset.StandardCharsets;
import java.util.Map;

/**
 * @author tie yan
 */
@RestControllerAdvice
public class GlobalRequestAdvice extends RequestBodyAdviceAdapter {

    /**
     * Invoked first to determine if this interceptor applies.
     *
     * @param methodParameter the method parameter
     * @param targetType      the target type, not necessarily the same as the method
     *                        parameter type, e.g. for {@code HttpEntity<String>}.
     * @param converterType   the selected converter type
     * @return whether this interceptor should be invoked or not
     */
    @Override
    public boolean supports(@NonNull MethodParameter methodParameter, @NonNull Type targetType, @NonNull Class<? extends HttpMessageConverter<?>> converterType) {
        return true;
    }


    /**
     * Invoked second before the request body is read and converted.
     *
     * @param inputMessage  the request
     * @param parameter     the target method parameter
     * @param targetType    the target type, not necessarily the same as the method
     *                      parameter type, e.g. for {@code HttpEntity<String>}.
     * @param converterType the converter used to deserialize the body
     * @return the input request or a new instance (never {@code null})
     */
    @Override
    @NonNull
    public HttpInputMessage beforeBodyRead(HttpInputMessage inputMessage, @NonNull MethodParameter parameter, @NonNull Type targetType, @NonNull Class<? extends HttpMessageConverter<?>> converterType) throws IOException {
        String requestBodyStr = Tools.ByteStream.readAll(inputMessage.getBody());
        if (MediaType.APPLICATION_JSON.isCompatibleWith(inputMessage.getHeaders().getContentType()) ||
                MediaType.TEXT_PLAIN.isCompatibleWith(inputMessage.getHeaders().getContentType())) {
            if (!Jsoup.isValid(requestBodyStr, Safelist.relaxed())) {
                throw new FrameworkException("invalid request content(xss)");
            }
        }
        String decryptRequired = inputMessage.getHeaders().getFirst("X-PASSWORD-ENCRYPT");
        if (Tools.Str.isNotBlank(decryptRequired)) {
            Encrypted encryptedAnnotation = parameter.getParameterAnnotation(Encrypted.class);
            if (null != encryptedAnnotation) {
                requestBodyStr = this.decode(requestBodyStr, encryptedAnnotation.fields());
            }
        }
        return this.newInputMessage(requestBodyStr, inputMessage);
    }

    private String decode(String originBody, String[] encryptedFields) {
        Map<String, Object> paramMap = Tools.Json.toMap(originBody);
        Map<String, Object> result = Tools.Json.toMap(originBody);
        // 获取请求密码并解密
        for (String encryptedField : encryptedFields) {
            if (paramMap.containsKey(encryptedField)) {
                result.put(encryptedField, Tools.Decrypt.instance("AES/CBC/PKCS7PADDING").aes((String) paramMap.get(encryptedField), "1234567890000000", "1234567890000000"));
            }
        }
        return Tools.Json.toJsonStr(result);
    }

    private HttpInputMessage newInputMessage(String requestBodyStr, HttpInputMessage inputMessage) {
        return new HttpInputMessage() {

            @NonNull
            @Override
            public InputStream getBody() {
                return new ByteArrayInputStream(requestBodyStr.getBytes(StandardCharsets.UTF_8));
            }

            @NonNull
            @Override
            public HttpHeaders getHeaders() {
                return inputMessage.getHeaders();
            }
        };
    }
}
